package net.cyl.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import net.cyl.config.RabbitMQConfig;
import net.cyl.constant.RedisKey;
import net.cyl.controller.request.TrafficPageRequest;
import net.cyl.controller.request.UseTrafficRequest;
import net.cyl.emums.BizCodeEnum;
import net.cyl.emums.EventMessageType;
import net.cyl.emums.TaskStateEnum;
import net.cyl.exception.BizException;
import net.cyl.feign.ProductFeignService;
import net.cyl.feign.ShortLinkFeignService;
import net.cyl.interceptor.LoginInterceptor;
import net.cyl.manager.TrafficManager;
import net.cyl.manager.TrafficTaskManager;
import net.cyl.model.EventMessage;
import net.cyl.model.LoginUser;
import net.cyl.model.TrafficDO;
import net.cyl.mapper.TrafficMapper;
import net.cyl.model.TrafficTaskDO;
import net.cyl.service.TrafficService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.cyl.util.JsonData;
import net.cyl.util.JsonUtil;
import net.cyl.util.TimeUtil;
import net.cyl.vo.ProductVO;
import net.cyl.vo.TrafficVO;
import net.cyl.vo.UseTrafficVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 流量包信息表 服务实现类
 * </p>
 *
 * @author 曹玉林
 * @since 2025-05-09
 */
@Service
@Slf4j
public class TrafficServiceImpl extends ServiceImpl<TrafficMapper, TrafficDO> implements TrafficService {
     @Autowired
    private TrafficManager trafficManager;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
     @Autowired
     private ProductFeignService productFeignService;
     @Autowired
     private TrafficTaskManager trafficTaskManager;
     @Autowired
     private RabbitMQConfig rabbitMQConfig;
     @Autowired
     private RabbitTemplate rabbitTemplate;
     @Autowired
     private ShortLinkFeignService shortLinkFeignService;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void handleTrafficMessage(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventType();
        if(EventMessageType.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)){

            //订单已经支付，新增流量

            String content = eventMessage.getMessageBody();
            Map orderInfoMap = JsonUtil.json2Obj(content,Map.class);

            //还原订单商品信息
            String outTradeNo = null;
            if (orderInfoMap != null) {
                outTradeNo = (String)orderInfoMap.get("outTradeNo");
            }
            Integer buyNum = null;
            if (orderInfoMap != null) {
                buyNum = (Integer)orderInfoMap.get("buyNum");
            }
            String productStr = null;
            if (orderInfoMap != null) {
                productStr = (String) orderInfoMap.get("product");
            }
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息:{}",productVO);


            //流量包有效期
            LocalDateTime expiredDateTime = null;
            if (productVO != null) {
                expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
            }
            Date date = null;
            if (expiredDateTime != null) {
                date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());
            }


            //构建流量包对象
            TrafficDO trafficDO = null;
            if (productVO != null) {
                trafficDO = TrafficDO.builder()
                        .accountNo(accountNo)
                        .dayLimit(productVO.getDayTimes() * buyNum)
                        .dayUsed(0)
                        .totalLimit(productVO.getTotalTimes())
                        .pluginType(productVO.getPluginType())
                        .level(productVO.getLevel())
                        .productId(productVO.getId())
                        .outTradeNo(outTradeNo)
                        .expiredDate(date).build();
            }

            int rows = trafficManager.add(trafficDO);
            log.info("消费消息新增流量包:rows={},trafficDO={}",rows,trafficDO);

            String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);
            redisTemplate.delete(totalTrafficTimesKey);

        } else if(EventMessageType.TRAFFIC_FREE_INIT.name().equalsIgnoreCase(messageType)){
            //发放免费流量包
            Long productId = Long.valueOf(eventMessage.getBizId());

            JsonData jsonData = productFeignService.detail(productId);

            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>(){});
            //构建流量包对象
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo("free_init")
                    .dayLimit(productVO.getDayTimes())
                    .expiredDate(new Date())
                    .build();

            trafficManager.add(trafficDO);

        } else if(EventMessageType.TRAFFIC_USED.name().equalsIgnoreCase(messageType)){
            //流量包使用，检查是否成功使用
            //检查task是否存在
            //检查短链是否成功
            //如果不成功，则恢复流量包
            //删除task (也可以更新task状态，定时删除就行)

            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);

            //非空且锁定
            if(trafficTaskDO !=null && trafficTaskDO.getLockState().equalsIgnoreCase(TaskStateEnum.LOCK.name())){

                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());

                if(jsonData.getCode()!= 0 ){

                    log.error("创建短链失败，流量包回滚");

                    String useDateStr =  TimeUtil.format(trafficTaskDO.getGmtCreate(),"yyyy-MM-dd");

                    trafficManager.releaseUsedTimes(accountNo,trafficTaskDO.getTrafficId(),1,useDateStr);

                    //恢复流量包，应该删除这个key（也可以让这个key递增）
                    String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);
                    redisTemplate.delete(totalTrafficTimesKey);

                }

                //多种方式处理task，不立刻删除，可以更新状态，然后定时删除也行
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId,accountNo);

            }



        }
    }

    @Override
    public Map<String, Object> pageAvailable(TrafficPageRequest request) {
        int size = request.getSize();
        int page = request.getPage();
        Long accountNo = LoginInterceptor.loginUserThreadLocal.get().getAccountNo();
        IPage<TrafficDO> trafficVOIPage = trafficManager.pageAvailable(page, size, accountNo);
        List<TrafficDO> trafficDOList = trafficVOIPage.getRecords();
        List<TrafficVO> trafficVOList = trafficDOList.stream().map(obj->beanProcess(obj)).collect(Collectors.toList());
        Map<String, Object> map  = new HashMap<>(3);
        map.put("total_record", trafficVOIPage.getTotal());
        map.put("total_page", trafficVOIPage.getPages());
        map.put("current_data", trafficVOList);
        return  map;
    }

    @Override
    public TrafficVO detail(long trafficId) {
        LoginUser loginUser = LoginInterceptor.loginUserThreadLocal.get();

        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, loginUser.getAccountNo());

        return beanProcess(trafficDO);
    }

    @Override

    public void deleteExpireTraffic() {
         trafficManager.deleteExpireTraffic();
    }
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public JsonData reduce(UseTrafficRequest useTrafficRequest) {

        Long accountNo =  useTrafficRequest.getAccountNo();

        //处理流量包，筛选出未更新流量包，当前使用的流量包
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);

        log.info("今天可用总次数:{},当前使用流量包:{}",useTrafficVO.getDayTotalLeftTimes(),useTrafficVO.getCurrentTrafficDO());
        if(useTrafficVO.getCurrentTrafficDO() == null){
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        log.info("待更新流量包列表:{}",useTrafficVO.getUnUpdatedTrafficIds());

        if(useTrafficVO.getUnUpdatedTrafficIds().size()>0){
            //更新今日流量包
            trafficManager.batchUpdateUsedTimes(accountNo,useTrafficVO.getUnUpdatedTrafficIds());
        }

        //先更新，再扣减当前使用的流量包
        int rows = trafficManager.addDayUsedTimes(accountNo,useTrafficVO.getCurrentTrafficDO().getId(),1);

        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder().accountNo(accountNo).bizId(useTrafficRequest.getBizId())
                .useTimes(1).trafficId(useTrafficVO.getCurrentTrafficDO().getId())
                .lockState(TaskStateEnum.LOCK.name()).build();

        trafficTaskManager.add(trafficTaskDO);


        if(rows != 1){
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        //往redis设置下总流量包次数，短链服务那边递减即可； 如果有新增流量包，则删除这个key
        long leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());

        String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);

        redisTemplate.opsForValue().set(totalTrafficTimesKey,
                 useTrafficVO.getDayTotalLeftTimes()-1,leftSeconds, TimeUnit.SECONDS);


        EventMessage trafficUseEventMessage = EventMessage.builder().accountNo(accountNo).bizId(trafficTaskDO.getId() + "")
                .eventType(EventMessageType.TRAFFIC_USED.name()).build();

        //发送延迟消息，用于异常回滚
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
                rabbitMQConfig.getTrafficReleaseDelayRoutingKey(),trafficUseEventMessage);

        return JsonData.buildSuccess();
     }

    private UseTrafficVO processTrafficList(Long accountNo) {

        //全部流量包
        List<TrafficDO> list = trafficManager.selectAvailableTraffics(accountNo);
        if(list == null || list.size()==0){ throw  new BizException(BizCodeEnum.TRAFFIC_EXCEPTION); }

        //天剩余可用总次数
        Integer dayTotalLeftTimes = 0;

        //当前使用
        TrafficDO currentTrafficDO = null;

        //没过期，但是今天没更新的流量包id列表
        List<Long> unUpdatedTrafficIds = new ArrayList<>();

        //今天日期
        String todayStr = TimeUtil.format(new Date(),"yyyy-MM-dd");

        for(TrafficDO trafficDO : list){
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(),"yyyy-MM-dd");
            if(todayStr.equalsIgnoreCase(trafficUpdateDate)){
                //已经更新  天剩余可用总次数 = 总次数 - 已用
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes = dayTotalLeftTimes+dayLeftTimes;

                //选取当次使用流量包
                if(dayLeftTimes>0 && currentTrafficDO==null){
                    currentTrafficDO = trafficDO;
                }

            }else {
                //未更新
                dayTotalLeftTimes = dayTotalLeftTimes + trafficDO.getDayLimit();
                //记录未更新的流量包
                unUpdatedTrafficIds.add(trafficDO.getId());

                //选取当次使用流量包
                if(currentTrafficDO == null){
                    currentTrafficDO = trafficDO;
                }

            }
        }

        UseTrafficVO useTrafficVO = new UseTrafficVO(dayTotalLeftTimes,currentTrafficDO,unUpdatedTrafficIds);
        return useTrafficVO;
    }

    private TrafficVO beanProcess(TrafficDO obj) {
        TrafficVO trafficVO = new TrafficVO();
        BeanUtils.copyProperties(obj,trafficVO);
        return trafficVO;
    }
}
